package com.mine.redis.util.service;

import cn.hutool.core.collection.CollectionUtil;
import com.mine.redis.util.bean.CouponDO;
import com.mine.redis.util.bean.OrderDO;
import com.mine.redis.util.mapper.CouponMapper;
import com.mine.redis.util.mapper.OrderMapper;
import com.mine.redis.util.utils.cache.CacheConstants;
import com.mine.redis.util.utils.id.RedisIdWorker;
import com.mine.redis.util.utils.lock.RedisLock;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

import static com.mine.redis.util.utils.cache.CacheConstants.SHOP_ORDER_KEY;

/**
 * @author weixiao
 * @date 2024/8/16 14:01
 */
@Service
public class CouponService {

    @Resource
    private CouponMapper couponMapper;

    @Resource
    private OrderMapper orderMapper;

    @Resource
    private RedisIdWorker redisIdWorker;

    @Resource
    private RedisLock lock;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    private static final DefaultRedisScript<Long> SECKILL_SCRIPT;

    private static final BlockingQueue<OrderDO> QUEUE = new ArrayBlockingQueue<>(1024 * 1024);

    static {
        SECKILL_SCRIPT = new DefaultRedisScript<>();
        SECKILL_SCRIPT.setLocation(new ClassPathResource("script/seckill.lua"));
        SECKILL_SCRIPT.setResultType(Long.class);
    }

    @PostConstruct
    public void init() {
        System.out.println("PostConstruct...");
        new Thread(() -> {
            while (true) {
                try {
                    OrderDO orderDO = QUEUE.take();
                    couponMapper.minusCoupon(orderDO.getCouponId());
                    orderMapper.buyCoupon(orderDO.getId(), orderDO.getUserId(), orderDO.getCouponId());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    public void secKillWithLua(Integer userId, Integer couponId) {
        // 1.调用lua脚本，完成下单前的判断以及下单操作
        String couponKey = "seckill:coupon:" + couponId;
        String orderKey = "seckill:order:" + couponId;
        Long result = stringRedisTemplate.execute(SECKILL_SCRIPT, CollectionUtil.newArrayList(couponKey, orderKey), userId.toString());
        // 2.下单成功后，将订单信息异步存入数据库
        if (result == null) {
            return;
        }
        int r = result.intValue();
        if (r != 0) {
            System.out.println("下单失败！" + "原因：" + (r == 1 ? "库存不足" : "重复下单"));
            return;
        }
        long orderId = redisIdWorker.nextId(CacheConstants.ORDER_ID_PREFIX);
        OrderDO orderDO = new OrderDO(orderId, userId, couponId);
        QUEUE.add(orderDO);
    }

    @Transactional(rollbackFor = Exception.class)
    public void secKill(Integer userId, Integer couponId) {
        String key = SHOP_ORDER_KEY + ":" + userId;
        boolean lockResult = lock.tryLock(key, 10L, TimeUnit.SECONDS);
        if (!lockResult) {
            System.out.println("购买失败，一个人只能购买一张优惠券");
            return;
        }
        try {
            // 0.判断用户之前是否购买过，如果购买过就不能再买
            Integer orderCount = orderMapper.countOrder(userId, couponId);
            if (orderCount != null && orderCount > 0) {
                System.out.println("购买失败，一个人只能购买一张优惠券");
                return;
            }
            // 1.直接扣减优惠券库存（SQL语句中的where条件会判断优惠券数量是否大于0）
            Integer res = couponMapper.minusCoupon(couponId);
            // 2.扣减成功，插入订单
            if (res != null && res > 0) {
                long id = redisIdWorker.nextId(CacheConstants.ORDER_ID_PREFIX);
                orderMapper.buyCoupon(id, userId, couponId);
                return;
            }
            // 3.扣减失败
            System.out.println("库存不足");
        } finally {
            lock.unlock(key);
        }
    }
}
